#include <linux/module.h>#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/version.h>
#include <net/tcp.h>
#include "/usr/src/linux/include/linux/netfilter_ipv4/ip_conntrack.h"
//#define __DEBUG__
#include "ipt_ipcontext.h"
#include "parser.h"
#include "log_trace.h"

enum enumstate {READY = 0, VERIFIED = 1, REJECTED = 2};
DEFINE_RWLOCK(ipapp_lock);

/*
 * match - this function handles the main packet level filtering and decides
 * what to do with the packet according to the rules passed to it.
 * Return values - 0 - doesn't match the packet, 1- matches the packet.
 */
static int
match(const struct sk_buff *skb,
      const struct net_device *in,
      const struct net_device *out,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
      const struct xt_match  *mymatch,
      const void *matchinfo,
      int offset,
      unsigned int myprotoff,
#else
      const void *matchinfo,
      int offset,
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
      const void *hdr,
      u_int16_t datalen,
#endif

      int *hotdrop)
{
	struct ipt_context_info *info = (struct ipt_context_info *)matchinfo;
	struct iphdr *ip = skb->nh.iph;
	struct ip_conntrack *ct;
	enum ip_conntrack_info ctinfo;

	/* If the packet data is fragmented, we don't handle it. (shouldn't
	   happen in 2.6 */
	if (offset)
	{
		OUT_ERROR("Offset is not 0 (fragmented packet?)");
		return 0;
	}

	/* If the packet isn't an IP TCP packet, we don't handle it. */
	if(!ip || ip->protocol != IPPROTO_TCP)
	{
		OUT_DEBUG("Finished Packet - not an IP tcp packet...");
		return 0;
	}

	/* Get the netfilter's conntrack info for this packet, abort if doesn't
	   exist. */
	ct = ip_conntrack_get(skb, &ctinfo);
	if (ct == NULL)
	{
		OUT_DEBUG("Packet has no conntrack");
		return 0;
	}

	OUT_DEBUG("Got Packet...");

	/* If the connection was already classified, return its state. */
	if (ct->contextinfo.state == VERIFIED)
	{
		OUT_DEBUG("Finished Packet - already verified...");
		return 0;
	}
	else
	if (ct->contextinfo.state == REJECTED)
	{
		OUT_DEBUG("Finished Packet - already rejected...");
		return 1;
	}
	/* The connection hasn't been classified yet, add its data to the
	   buffer and try to classify it. */
	else
	{
		unsigned char *payload = (char *)ip+(ip->ihl*4);
		int hlen = ntohs(ip->tot_len)-(ip->ihl*4);
		if (ip->protocol == IPPROTO_TCP)
		{
			regexp *compile;
			struct tcphdr *tcph = (struct tcphdr *)(&skb->data[ip->ihl * 4]);
			int psrc = ntohs(tcph->source);
			int pdst = ntohs(tcph->dest);
			/* Check if the given match rule applies to the packet.
			   If not, don't match the packet. */
			if ((psrc != info->port) && (pdst != info->port))
			{
				OUT_DEBUG("Finished Packet - no rule...");
				return 0;
			}
			/* Get the packet's data. If it's empty don't match. */
			payload += tcph->doff * 4;
			hlen -= tcph->doff * 4;
			if (hlen == 0)
			{
				return 0;
			}
			/* Lock the conntrack entry (for multi-threading
			   issues). */
			write_lock(&ipapp_lock);

			/* If someone happened to classify the packet while we
			   were waiting for the lock, do as they decided. */
			if (ct->contextinfo.state == REJECTED)
			{
				write_unlock(&ipapp_lock);
				OUT_DEBUG("Finished Packet - someone else rejected...");
				return 1;
			}
			else if (ct->contextinfo.state == VERIFIED)
			{
				write_unlock(&ipapp_lock);
				OUT_DEBUG("Finished Packet - someone else verified...");
				return 0;
			}

			/* Put the packet's data into the buffer we were told
			   to use (in the rule), can be input or output and
			   handle buffer overflow. */
			if (!(info->flags & IPCONTEXT_IN))
			{
				if (ct->contextinfo.out_cnt + hlen > CONTEXT_BUFFER_SIZE - 1)
					hlen = CONTEXT_BUFFER_SIZE - 1 - ct->contextinfo.out_cnt;
				if (hlen > 0)
				{
					memcpy(&ct->contextinfo.out_buff[ct->contextinfo.out_cnt], payload, hlen);
					ct->contextinfo.out_cnt += hlen;
				}
			}
			else
			{
				if (ct->contextinfo.in_cnt + hlen > CONTEXT_BUFFER_SIZE - 1)
					hlen = CONTEXT_BUFFER_SIZE - 1 - ct->contextinfo.in_cnt;
				if (hlen > 0)
				{
					memcpy(&ct->contextinfo.in_buff[ct->contextinfo.in_cnt], payload, hlen);
					ct->contextinfo.in_cnt += hlen;
				}
			}

			/* Compile the regular expression, if there was a
			   problem, exit and don't match the packet. */
			compile = reg_compile(info->regular_exp);
			if (compile == NULL)
			{
				OUT_ERROR("failed compiling regexp");
				write_unlock(&ipapp_lock);
				return 0;
			}
			/* Run the regular expression on the appropriate buffer 
			   and classify it accordingly. */
			if (((info->flags & IPCONTEXT_IN) && parser(compile, ct->contextinfo.in_buff, ct->contextinfo.in_cnt)) || 
			   (!(info->flags & IPCONTEXT_IN) && parser(compile, ct->contextinfo.out_buff, ct->contextinfo.out_cnt)))
			{
				ct->contextinfo.state = VERIFIED;
				OUT_DEBUG("connection verified!");
				log_session(PACKAGE_ACCEPT, info,    in,out,skb);
			}
			else
			{
				ct->contextinfo.state = REJECTED;
				OUT_DEBUG("connection rejected!");
				log_session(PACKAGE_DENY, info,      in,out,skb);
			}
			/* Clean up. */
			kfree(compile);
			write_unlock(&ipapp_lock);
			OUT_DEBUG("Connection length: [in=%d,out=%d]", ct->contextinfo.in_cnt, ct->contextinfo.out_cnt);
		}
	}
	/* Return the verdict. */
	OUT_DEBUG("Finished Packet...");
	if (ct->contextinfo.state == REJECTED)
		return 1;
	else
		return 0;
}

/*
 * checkentry - Make sure the rule passed from user-mode is valid.
 */
static int
checkentry(const char *tablename,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
           const void *ip, 
           const struct xt_match *mymatch,
#else
           const struct ipt_ip *ip,
#endif
	   void *matchinfo,
	   unsigned int matchsize,
	   unsigned int hook_mask)
{
	if (matchsize != IPT_ALIGN( sizeof(struct ipt_context_info)))
	{
		OUT_ERROR("Matchsize differ, you may have forgotten to recompile me.");
		return 0;
	}

	OUT_DEBUG("Registered in the %s table, hook=%x, proto=%u",
                    tablename, hook_mask, ip->proto);

	return 1;
}

/*
 * ipcontext_match - Netfilter's registration struct.
 */
static struct ipt_match ipcontext_match = { 
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	{ NULL, NULL }, 
	"ipcontext", 
	&match, 
	&checkentry, 
	NULL, 
	THIS_MODULE
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17))
	.name		= "ipcontext",
	.match		= &match,
	.checkentry	= &checkentry,
	.me		= THIS_MODULE,
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
	.name		= "ipcontext",
	.match		= &match,
	.family         = AF_INET,
	.matchsize      = sizeof(struct ipt_context_info),
	.checkentry	= &checkentry,
	.me		= THIS_MODULE,
#endif
};

static int __init init(void)
{
	OUT_INFO("IPTables Application filtering extention loading...");
	return ipt_register_match(&ipcontext_match);
}
	
static void __exit fini(void)
{
	ipt_unregister_match(&ipcontext_match);
	OUT_INFO("IPTables Application filtering extention unloading...");
}

MODULE_AUTHOR("OS project ( Technion, Winter 2007 )");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("iptables application layer match module");
MODULE_VERSION("0.1");

module_init(init);
module_exit(fini);
